package com.psbc.redislock.lock.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

/**
 * redis锁，添加看门狗和自旋功能
 */
@Slf4j
public class RedisLock {
    private final int expireTime = 10; // 锁的过期时间，单位：秒
    private final int watchdogInterval = 5; // 看门狗检测间隔，单位：秒
    private final int expireTimeInSeconds = 2; // 尝试获取锁的时间
    //如果key存在，则续期，如果不存在则什么都不做
    private final String watchdogScript =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('expire', KEYS[1], tonumber(ARGV[2])) " +
                    "else return 0 end";
    private final String lockKey;
    private final RedisTemplate<String, Object> redisTemplate;
    private Timer TIMER = new Timer();

    public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
        this.lockKey = lockKey;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 在有限的时间内多次获取锁，如果没有成功获取到，
     * 则等待100毫秒后继续获取，直到超过时间限制
     * @return
     */
    public boolean acquireLock() {
        //记录获取锁的开始时间
        long start = System.currentTimeMillis();
        //如果获取锁失败可再次获取
        while (System.currentTimeMillis() - start < expireTimeInSeconds * 1000) {
            //获取锁
            Boolean getLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", expireTime, TimeUnit.SECONDS);
            if (getLock) {
                startWatchdog(); // 启动看门狗
                return true; // 获取锁成功
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return false;
    }

    /**
     * 删除锁
     */
    public void releaseLock() {
        redisTemplate.delete(lockKey);
    }

    //开启看门狗，用于实现锁的自动续期
    private void startWatchdog() {
        TIMER.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    Long result = redisTemplate.execute(RedisScript.of(watchdogScript, Long.class), Collections.singletonList(lockKey), "lock", (long) expireTime);
//                    log.info("看门狗续期返回：{}", result);
                    if (Objects.equals(result, 1L)) {
                        startWatchdog();
                    }
                } catch (Exception e) {
                    log.error("redis锁延长失败！", e);
                }
            }
        }, watchdogInterval * 1000);
    }
}
